package com.cee.admin.common;


import com.alibaba.excel.exception.ExcelAnalysisException;
import com.cee.admin.config.shiro.jwt.RefreshTokenException;
import com.cee.admin.config.shiro.jwt.RefreshTokenFailException;
import com.cee.common.core.context.FileCleanContext;
import com.cee.common.core.vo.MultiErrorMsgVo;
import com.cee.common.disk.FileServiceException;
import com.cee.common.easyexcel.ExcelFormatException;
import com.cee.common.exception.*;
import com.cee.common.spring.SpringConfigUtil;
import com.cee.common.utils.CollectionUtils;
import com.cee.common.web.result.*;
import com.cee.ds.common.utils.BasePrincipalUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authz.UnauthenticatedException;
import com.cee.common.exception.UnauthorizedException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import java.util.Set;

import static com.cee.common.spring.SpringConfigUtil.isDevEnv;
import static com.cee.common.web.result.ResultCode.*;


/**
 * 处理 ajax 请求异常
 *
 * @author rxliuli
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler implements ResultHelper {

    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public Result<String> handleHttpMessageNotReadableException(HttpServletResponse response, HttpMessageNotReadableException e) throws Exception {
        if (null != e.getCause()) {
            Throwable cause = e.getCause();
            if (cause instanceof InvalidDefinitionException) {
                return handlerAjaxError(response, e, new CustomWebResult(ResultCode.PARAM_TYPE_ERROR.getCode(), "Type definition error"));
            }
            if (cause instanceof JsonProcessingException) {
                return handlerAjaxError(response, e, new CustomWebResult(ResultCode.PARAM_TYPE_ERROR.getCode(), "JSON parse error"));
            }
        }
        return handlerAjaxError(response, e, DATA_HANDLE_ERROR);
    }

    @ExceptionHandler(value = DataProcessException.class)
    public Result<String> handleDataProcessException(HttpServletResponse response, DataProcessException e) throws Exception {
        return handlerAjaxError(response, e, DATA_HANDLE_ERROR);
    }

    @ExceptionHandler(value = {MultiErrorMsgException.class})
    public Result<MultiErrorMsgVo> handlerHitException(MultiErrorMsgException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        return success(ResultCode.MULTI_ERROR_MSG, e.getMultiErrorMsgVo());
    }

    @ExceptionHandler({TypeMismatchException.class})
    public Result<String> handlerTypeMismatchException(TypeMismatchException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.warn("数据类型不匹配", e);
        return error(e.getMessage());
    }


    /**
     * 自定义的全局异常（手动抛出）
     *
     * @param e 异常
     */
    @ExceptionHandler({BindException.class})
    public Result<String> handlerBindException(BindException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.warn("请求参数不合法", e);
        if (e.getFieldError() != null && e.getFieldError().getDefaultMessage() != null) {
            return error(e.getFieldError().getDefaultMessage());
        }
        return error(PARAM_IS_INVALID);
    }

    @ExceptionHandler({ConstraintViolationException.class})
    public Result<String> handlerConstraintViolationException(ConstraintViolationException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.warn("请求参数不合法", e);
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        if (SpringConfigUtil.isDevEnv() || CollectionUtils.isEmpty(constraintViolations)) {
            return error(e.getMessage());
        }
        for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            return error(constraintViolation.getMessage());
        }
        return error(PARAM_NOT_VALID);
    }

    @ExceptionHandler({ProhibitedActionException.class})
    public Result<String> handleProhibitedActionException(ProhibitedActionException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error("拦截了一个被禁止的操作，用户id：{}，异常信息：", BasePrincipalUtils.currentUserId(), e);
        return error(NOT_PERMISSION);
    }


    @ExceptionHandler({DuplicateKeyException.class})
    public Result<?> handleDuplicateKeyException(DuplicateKeyException e, HttpServletResponse response) {
        log.error(e.getMessage(), e);
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        return error(EXISTED_DATA_NOTE_UPDATE.getMsg());
    }

    //------- EXCEL start

    @ExceptionHandler({ExcelTemplateException.class})
    public Result<String> handleExcelTemplateException(ExcelTemplateException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, ResultCode.EXCEL_TEMPLATE_ERROR);
    }

    @ExceptionHandler({ExcelAnalysisException.class})
    public Result<?> handleExcelAnalysisException(ExcelAnalysisException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        Throwable cause = e.getCause();
        if (cause instanceof GlobalException) {
            return error(cause.getMessage());
        }
        return error("数据解析异常，请检查数据类型或大小是否符合规范");
    }

    @ExceptionHandler({ExcelParseException.class})
    public Result<?> handleExcelParseException(ExcelParseException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(e.getMessage());
    }

    @ExceptionHandler({ExcelFormatException.class})
    public Result<String> handleExcelFormatException(ExcelFormatException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        if (e.getFilePath() != null) {
            return build(EXCEL_FILE_WITH_COMMENTS, "excel内容不符合规范，详情请看错误批注文件", e.getFilePath());
        } else {
            return error(EXCEL_FILE_WITH_COMMENTS.getMsg());
        }
    }

    //------- EXCEL end

    @ExceptionHandler({ArithmeticException.class})
    public Result<?> handleArithmeticException(ArithmeticException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error("算数异常");
    }


    @ExceptionHandler({DiskServiceException.class, FileServiceException.class})
    public Result<?> handleDiskServiceException(Exception e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error("文件服务异常：",e);
        return error(FILE_SERVICE_ERROR);
    }

    /**
     * 登录验证异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ExpiredCredentialsException.class)
    public Result<?> handleExpiredCredentialsException(ExpiredCredentialsException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(NOT_LOGIN);
    }

    /**
     * 被禁用账户异常
     * @param e
     * @return
     */
    @ExceptionHandler(DisabledAccountException.class)
    public Result<?> handleDisabledAccountException(DisabledAccountException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(USER_ACCOUNT_DISABLE);
    }

    /**
     * 刷新凭证异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RefreshTokenException.class)
    public Result<?> handleRefreshTokenException(RefreshTokenException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(REFRESH_TOKEN);
    }

    /**
     * 刷新token失败
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RefreshTokenFailException.class)
    public Result<?> handleRefreshTokenFailException(RefreshTokenFailException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(REFRESH_FAIL_TOKEN);
    }

    @ExceptionHandler(UnauthenticatedException.class)
    public Result<String> handlerUnauthenticatedException(UnauthenticatedException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, USER_NOT_LOGIN);
    }

    @ExceptionHandler(org.apache.shiro.authz.UnauthorizedException.class)
    public Result<String> handlerShiroUnauthenticatedException(org.apache.shiro.authz.UnauthorizedException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, NOT_PERMISSION);
    }

    @ExceptionHandler(UnauthorizedException.class)
    public Result<String> handlerUnauthenticatedException(UnauthorizedException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, new CustomWebResult(NOT_PERMISSION.getCode(), e.getMessage()));
    }

    /**
     * 请求方法类型不正确
     *
     * @param e
     * @param response
     * @return
     * @throws Exception
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<String> handlerHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, HttpStatus.METHOD_NOT_ALLOWED, e.getMessage());
    }

    /**
     * 404 异常（找不到处理）
     *
     * @param e 异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<String> handlerNoHandlerFoundException(NoHandlerFoundException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, HttpStatus.NOT_FOUND, "请求接口路径错误");
    }

    /**
     * 文件上传解析失败
     *
     * @param e 异常
     */
    @ExceptionHandler(MultipartException.class)
    public Result<String> handlerException400(MultipartException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, HttpStatus.PAYLOAD_TOO_LARGE, "文件上传失败");
    }

    /**
     * 自定义的全局异常（手动抛出）
     *
     * @param e 异常
     */
    @ExceptionHandler({GlobalException.class})
    public Result<String> handlerExceptionGlobal(GlobalException e, HttpServletResponse response) throws Exception {
        Integer code = e.getCode()==null?500:e.getCode();
        String message = e.getMessage();
        CustomWebResult customResultCode = new CustomWebResult(code,message);
        return handlerAjaxError(response, e, customResultCode);
    }

    public Result<String> getResult(GlobalException e) {
        FileCleanContext.stopClean(); FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(NOT_LOGIN);
    }


    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public Result<String> handlerHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(UNSUPPORTED_CONTEND_TYPE, getHttpMediaTypeNotAcceptableExceptionMsg(e));
    }
    private String getHttpMediaTypeNotAcceptableExceptionMsg(HttpMediaTypeNotAcceptableException e) {
        return new StringBuilder("支持的content type为:").append(e.getSupportedMediaTypes()).toString();
    }

    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public Result<String> handlerHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        log.error(e.getMessage(), e);
        return error(UNSUPPORTED_CONTEND_TYPE,getHttpMediaTypeNotSupportedExceptionErrorMsg(e));
    }

    private String getHttpMediaTypeNotSupportedExceptionErrorMsg(HttpMediaTypeNotSupportedException e) {
        return new StringBuilder("不支持").append(e.getContentType().getSubtype()).append("。支持的content type：").append(e.getSupportedMediaTypes()).toString();
    }

    @ExceptionHandler({MissingServletRequestParameterException.class})
    public Result<String> handlerMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        return error(PARAM_NOT_COMPLETE, PARAM_NOT_COMPLETE.getMsg());
    }

    @ExceptionHandler({DataIntegrityViolationException.class})
    public Result<String> handlerDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletResponse response) throws Exception {
        return handlerAjaxError(response, e, HttpStatus.INTERNAL_SERVER_ERROR, "数据完整性校验失败");
    }

    /**
     * 500 异常（内部错误）
     *
     * @param e 异常
     */
    @ExceptionHandler
    public Result<String> handlerException500(Exception e, HttpServletResponse response) throws Exception {
        if (e.getCause() != null && (e.getCause() instanceof GlobalException || e.getCause() instanceof com.cee.common.exception.GlobalException)) {
            Integer code = ((GlobalException) e.getCause()).getCode()==null?500: ((GlobalException) e.getCause()).getCode();
            String message = e.getCause().getMessage();
            CustomWebResult customResultCode = new CustomWebResult(code,message);
            return handlerAjaxError(response, e, customResultCode);
        }
        return handlerAjaxError(response, e, HttpStatus.INTERNAL_SERVER_ERROR, "系统内部错误");
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        log.error(e.getMessage(), e);
        if (isDevEnv()) {
            return error(e.getMessage());
        }
        FieldError fieldError = e.getBindingResult().getFieldError();
        return error(PARAM_NOT_VALID,  fieldError == null ? PARAM_NOT_VALID.getMsg() : fieldError.getDefaultMessage());
    }


    /**
     * 全局错误处理
     *
     * @param response http 响应
     * @param e        所捕获的异常类型
     * @param status   http 状态
     * @return 返回一个 ModelAndView（ajax 请求时返回 null）
     * @throws Exception 可能会不处理而直接抛出异常
     */
    private Result<String> handlerAjaxError(HttpServletResponse response, Exception e, HttpStatus status, String defaultMessage) throws Exception {
        return handlerAjaxError(response, e, status.value(), defaultMessage);
    }

    private Result<String> handlerAjaxError(HttpServletResponse response, Exception e, int code, String defaultMessage) throws Exception {
        return handlerAjaxError(response, e, new CustomWebResult(code,defaultMessage));
    }

    private Result<String> handlerAjaxError(HttpServletResponse response, Exception e,WebResult webResult) throws Exception {
        FileCleanContext.stopClean();
        FileCleanContext.asyncStartCleanWhenException();
        //如果拦截的请求方法所在 Controller 存在 @ResponseStatus 注解时，则不应对其进行处理（越靠近真正执行方法的处理优先级就越高！）
        //@ResponseStatus 是使用在类上的注解，当类中存在未经处理的异常时就会返回指定的 HttpStatus 作为 HttpResponse
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }
        //此处应该记录错误日志
        log.error(e.getMessage(), e);
        //这两句防止中文乱码
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(200);
        return ResultBuilder.error(webResult);
    }

}
